#include "PageProcessor.h"

// -------------------- 内部工具函数: 去除网页内容里的html标签---------------------
static string removeHtmlTags(const string& html) { 
    std::regex annotation("<!--[\\s\\S]*?-->"); // 匹配所有 HTML 注释
    std::regex tags("<[^>]*>");                 // 匹配所有 HTML 标签

    string temp = std::regex_replace(html, annotation, "");  
    return std::regex_replace(temp, tags, "");
}


// 构造函数
PageProcessor::PageProcessor(const string& stopWordsFile) 
{
    // 打开停用词文件
    ifstream ifs{stopWordsFile};
    if (!ifs.is_open()) {
        std::cerr << stopWordsFile << ": File open failed.\n";
        exit(1);
    }

    // 将停用词存入相应的set数据成员
    string word;
    while (ifs >> word) {
        m_stopWords.insert(word);
    }

    ifs.close();
}


// 外部接口
// 入参：原始网页文件(.xml)所在目录
void PageProcessor::process(const std::string& dir) 
{
    // 提取指定目录下的所有网页内容
    extract_documents(dir);

    // 对提取到的网页内容进行去重（去除相似网页）
    deduplicate_documents();

    // 生成网页库和网页偏移库
    build_pages_and_offsets(libOutDir + "pageLib.txt", libOutDir + "pageOffsetLib.txt");

    // 生成倒排索引库
    build_inverted_index(libOutDir + "inverted_index.txt");
}


// 提取指定目录下所有网页文件的内容，存入m_documents
void PageProcessor::extract_documents(const string& dir)
{
    using namespace tinyxml2;

    // 获取目录下所有文件名
    vector<string> fileNames;
    try {
        fileNames = DirectoryScanner::scan(dir);
    } catch (const string& errmsg) {
        std::cerr << dir  << ": "<< errmsg << std::endl;
        exit(1);
    }

    // 依次解析每个xml文件
    for (auto & file : fileNames) {
        string filename = dir + "/" + file;
        
        XMLDocument doc;
        
        // 加载 XML 文件
        if (doc.LoadFile(filename.c_str()) != XML_SUCCESS) {
            std::cerr << filename << ": File cannot be parsed" << std::endl;
            continue;
        }
        
        // 获取根节点 <rss>
        XMLElement* root = doc.FirstChildElement("rss");
        if (!root) continue;
        
        // 进入 <channel>
        XMLElement* channel = root->FirstChildElement("channel");
        if (!channel) continue;
        
        // 遍历所有 <item>
        XMLElement* item = channel->FirstChildElement("item");
        while (item) {
            Document current;  // 存储提取到的单个item的信息
              
            // 提取链接字段
            if (auto elem = item->FirstChildElement("link")) {
                current.link = elem->GetText() ? elem->GetText() : "";
            }

            // 提取标题字段
            if (auto elem = item->FirstChildElement("title")) {
                current.title = elem->GetText() ? elem->GetText() : "";
            }
            
            // 提取文章内容字段
            // 文章内容：优先使用 <content>（或<content:encoded>）；如果没有，则使用 <description>
            // 如果没有 <content>或<description>，则丢弃这一整个item
            if (auto elem = item->FirstChildElement("content")) {
                current.content = elem->GetText() ? elem->GetText() : "";
                current.content = removeHtmlTags(current.content); // 移除HTML标签
            } 
            else if (auto elem = item->FirstChildElement("content:encoded")) {
                current.content = elem->GetText() ? elem->GetText() : "";
                current.content = removeHtmlTags(current.content); 
            } 
            else if (auto elem = item->FirstChildElement("description")) {  // 使用 <description>
                current.content = elem->GetText() ? elem->GetText() : "";
                current.content = removeHtmlTags(current.content); 
            } 
            else { // 丢弃这一整个item
                item = item->NextSiblingElement("item");
                continue;
            }
            
            m_documents.push_back(std::move(current));
            item = item->NextSiblingElement("item");
        }
    }
}


// 对m_documents进行去重
void PageProcessor::deduplicate_documents() 
{
    #if DEBUG
    std::cout << "documents_size: " << m_documents.size() << std::endl;
    #endif

    std::vector<Document> unique_documents;     // 去重后的文件数组
    std::unordered_set<uint64_t> hash_seen;     // 记录已经出现过的唯一哈希值
    
    int newID = 0; // 新的文件编号

    // 依次为每个文件计算哈希值
    for (auto& document : m_documents) {
        string text = document.content;
        uint64_t hashcode;
        m_hasher.make(text, 5, hashcode);  // 计算哈希值
        
        bool is_duplicate = false;

        // 遍历已存在的哈希值，确认是否与当前新的哈希值相同
        for (auto& existingHash : hash_seen) {
            if (m_hasher.isEqual(existingHash, hashcode)) {
                is_duplicate = true;
                break;
            }
        }
        
        // 独特的哈希值
        if (!is_duplicate) {
            hash_seen.insert(hashcode);
            unique_documents.push_back(std::move(document));
            unique_documents.back().id = ++newID;
        }
    }
    
    m_documents = std::move(unique_documents);

    #if DEBUG
    std::cout << "documents_size after deduplication: " << m_documents.size() << std::endl;
    #endif
}


// 根据m_documents，生成网页库和网页偏移库
// 入参：两个生成文件的文件名
void PageProcessor::build_pages_and_offsets(const string& pageFile, const string& offsetFile) 
{
    // 网页库文件
    ofstream ofsPage(pageFile);
    if (!ofsPage.is_open()) {
        std::cerr << pageFile << ": File open failed.\n"
                  << "Failed to generate the webpage-library and webpage-offset-library files\n";
        return;
    }

    // 网页偏移库文件
    ofstream ofsOffset(offsetFile);
    if (!ofsOffset.is_open()) {
        std::cerr << offsetFile << ": File open failed.\n"
                  << "Failed to generate the webpage-library and webpage-offset-library files\n";
        return;
    }
    
    // 开始生成两个库文件
    size_t bytesWritten = 0;  // 已经写入网页库的总字节数
    for (size_t i = 0; i < m_documents.size(); ++i) {
        // 记录写入前的文件位置
        std::streampos before = ofsPage.tellp();

        // 写入一个文档到网页库
        const Document& item = m_documents[i];
        ofsPage << "<doc>\n"
                << "   <id>" << i + 1 << "</id>\n"
                << "   <link>" << item.link << "</link>\n"
                << "   <title>" << item.title << "</title>\n"
                << "   <content>" << item.content << "</content>\n"
                << "</doc>\n";
        
        // 记录写入后的文件位置
        std::streampos after = ofsPage.tellp();

        // 本轮写入网页库的字节数
        size_t docSize = after - before;

        // 写入一个记录到网页偏移库
        // 格式：<文档 id>  <偏移量>  <文档大小>
        ofsOffset << i + 1 << " "
                  << bytesWritten << " "
                  << docSize << "\n";
        bytesWritten += docSize;
    }

    ofsPage.close();
    ofsOffset.close();
}


// 创建倒排索引库
void PageProcessor::build_inverted_index(const string& filename) 
{   
    // 打开输出文件
    ofstream ofs(filename);
    if (!ofs.is_open()) {
        std::cerr << filename << ": File open failed.\n"
                  << "Failed to generate the inverted-index-library files\n";
        return;
    }

    // 记录关键词出现于哪些文档中（记录文档ID）
    std::unordered_map<string, vector<int>> word_docIDs; 

    // 记录关键词在单个文档中出现的频率（即TF）
    // 例如 word_TFs["app"][1] 的值，表示"app"在id为1的文档中出现的频率
    std::unordered_map<string, map<int, double>> word_TFs; 

    // 依次读取每篇文档的内容，并分割出其中的词语进行统计
    for (const auto& doc : m_documents) {
        double keyWordNum = 0;           // 单个文档中所有关键字的出现次数之和
        map<string, double> keyWord_count;  // 关键词及其出现次数
        vector<string> words;            // 所有词语（关键词 + 非关键词）

        // 清洗掉非中文的字符
        string content = ChineseExtractor::extract(doc.title + doc.content);
        // 清洗掉空白、英文、数字
        // string content = std::regex_replace(doc.content, std::regex("[\\s[:punct:][:alnum:]]+"), "");
        
        // 分词 
        m_tokenizer.Cut(content, words);
        
        // 遍历一篇文档中的所有词
        for (const auto& word: words) {
            // 不统计停用词
            if (m_stopWords.count(word) != 0) {
                continue;
            }
            // 统计关键词
            keyWord_count[word] += 1; // 在一篇文档中出现的次数
            ++keyWordNum;
        }

        // 对当前文档关键词进行数据统计
        for (auto& [word, count] : keyWord_count) {
            // 记录包含该关键词的文档id
            word_docIDs[word].push_back(doc.id); 
            // 计算该关键字在当前文档中的Term Frequency
            word_TFs[word][doc.id] = count / keyWordNum;
        }
    }

    // 计算倒排索引
    double docNum = m_documents.size();
    // a. 计算每个关键词的初始权重: w = TF * log2(N/DF)
    for (auto& [word, docIDs]: word_docIDs) {
        double DF = word_docIDs[word].size();
        // 计算当前关键词对于各个所属文档的权重w
        for (auto id : docIDs) {
            m_invertedIndex[word][id] = word_TFs[word][id] * std::log2(docNum / DF);
        }  
    }
    
    // b. 以文档为单位，统计一篇文档中所有关键词的初始权重w平方和的二次根式值
    vector<double> docWeights{docNum + 1}; 
    for (int id = 1; id <= docNum; ++id) {
        for (auto& [word, idToWeight] : m_invertedIndex) {
            auto it = idToWeight.find(id);
            if (it != idToWeight.end()) {
                docWeights[id] += pow(it->second, 2);  
            }
        }
        docWeights[id] = sqrt(docWeights[id]);
    }

    // c. 计算每个关键词的归一化权重w'
    for (auto& [word, docIDs]: word_docIDs) {
        ofs << word << " ";

        for (auto id : docIDs) {
            double weight = m_invertedIndex[word][id] / docWeights[id];
            m_invertedIndex[word][id] = weight;
            ofs << id << " " << weight << " ";
        }  
        ofs << "\n";
    }

    ofs.close();
}